#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <sys/time.h>

#include "sha1.h"
#include "finfo.h"
#include "fatal.h"


#define SHA1_BUFSIZE    (1024 * 1024)

#define text printf

#define getsec(_new, _old)      ((_new.tv_sec - _old.tv_sec) + (_new.tv_usec - _old.tv_usec) / 1000000.0)
#define getmsec(_new, _old)     ((_new.tv_sec - _old.tv_sec) * 1000 + (_new.tv_usec - _old.tv_usec) / 1000)
#define tvcopy(_dst, _src)      ((_dst.tv_sec = _src.tv_sec) , (_dst.tv_usec = _src.tv_usec))

#define getmb(_byte)            ((_byte) / 1024 / 1024)
#define getper(_count, _total)  (int)((_count) * 100 / (_total))


int sha1sum(char *path, char *out/* 40char + '\0' */)
{
    int fd = open(path, O_RDONLY);
    if (fd < 0) {
        return -1;
    }

    uint8_t *buf = (uint8_t *)malloc(SHA1_BUFSIZE);
    if (buf == NULL) {
        return -2;
    }

    sha1_ctx_t ctx;
    sha1_init(&ctx);

    for (int len; ; ) {
        len = read(fd, buf, SHA1_BUFSIZE);
        if (len < 0) {
            return -3;
        }
        if (len == 0) {
            break;
        }
        sha1_update(&ctx, (uint8_t *)buf, len);
    }
    // printf("loop: %d, size=%lu\n", i, size);

    unsigned char result[20] = {0};
    sha1_final(&ctx, result);
    free(buf);
    close(fd);

    char *p = out;
    for (int i = 0; i < 20; i++) {
        p += sprintf(p, "%02x", result[i]);
    }

    return 0;
}

int sha1ui(char *name, char *out)
{
    struct timeval start, blink, now;

    struct stat st = {0};
    if (stat(name, &st) < 0)
        return -1;

    uint64_t total = st.st_size;

    int fd = open(name, O_RDONLY);
    if (fd < 0) {
        return -1;
    }

    uint8_t *buf = (uint8_t *)malloc(SHA1_BUFSIZE);
    if (buf == NULL) {
        return -2;
    }

    sha1_ctx_t ctx;
    sha1_init(&ctx);

    uint64_t count = 0;
    int ui_flag = 0;
    gettimeofday(&start, NULL);
    tvcopy(blink, start);

    for (int len; ; ) {
        len = read(fd, buf, SHA1_BUFSIZE);
        if (len < 0) {
            return -3;
        }
        if (len == 0) {
            break;
        }
        sha1_update(&ctx, (uint8_t *)buf, len);
        count += len;

        // ui start
        gettimeofday(&now, NULL);
        if (getmsec(now, blink) < 500 && ui_flag != 0)
            continue;
        ui_flag = 1;

        text("\r-  check:  %d%%  %luMB  %.1fs  ", getper(count, total), getmb(count), getsec(now, start));
        fflush(stdout);

        tvcopy(blink, now);
    }
    gettimeofday(&now, NULL);
    text("\r-  check:  %d%%  %luMB  %.1fs  ", getper(count, total), getmb(count), getsec(now, start));
    text("\n");

    unsigned char result[20] = {0};
    sha1_final(&ctx, result);
    free(buf);
    close(fd);

    char *p = out;
    for (int i = 0; i < 20; i++) {
        p += sprintf(p, "%02x", result[i]);
    }

    return 0;
}

void show(finfo_t *info)
{
    printf("down: %s\n" "name: %s\n" "size: %s\n" "mode: %s (%o)\n" "sha1: %s\n",
            info->down, info->name, info->size, info->mode, atoi(info->mode), info->sha1);
}

int get_fileinfo(char *name, finfo_t *info)
{
    int ret = 0;
    // memset(info, 0, sizeof(finfo_t));

    /* basename */
    strcpy(info->name, basename(name));

    /* filesize */
    struct stat st = {0};
    ret = stat(name, &st);
    if (ret < 0)
        fatal("file info: stat (%d)", ret);

    sprintf(info->size, "%lu", st.st_size);

    sprintf(info->mode, "%d", st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));

    /* sha1sum */
    ret = sha1sum(name, info->sha1);
    if (ret < 0)
        fatal("file info: sha1 (%d)", ret);

    return 0;
}


#ifdef TEST_FINFO
int main(int argc, char *argv[])
{
    if (argc != 2) {
        return -1;
    }

    char *file = argv[1];

    char info[2048] = {0};

    get_file_info2(file, info);

    printf("%s\n", info);

    return 0;
}
#endif
